package bnetwork;

import java.math.BigDecimal;

public class CopyOfNode {

	String name;
	int nodeValue;
	int type;
	int domain;
	CopyOfNode[] deps;			//variables which this node depends on
	double[][] varprob;

	//probability for
	//varprob[x][y] = the probability for this Node to be valued y, when the values of the other nodes are valued by x
	//when x is SIGMA{i}(deps[i].value*PI{j<i}(deps[j].domain))
	public CopyOfNode(String name, int domain, CopyOfNode[] deps, double[][] varprob){
		this.name = name;
		this.domain = domain;
		this.deps = deps;
		this.varprob = varprob;
	}

	private double computeProb(int value){
		//		System.out.println(name);
		if (deps==null){
			//			System.out.println("P("+name+"="+value+") = " + varprob[0][value]);
			return varprob[0][value];
		}

		double ans = 0;

		for (int i=0; i<varprob.length; i++){
			//			System.out.println(name+" " + i+":");
			int tmp = i;
			double tmpans = 1;
			for (int j=0; j<deps.length; j++){
				int valuej = tmp % deps[j].domain;
				tmpans = tmpans*deps[j].computeProb(valuej);
				tmp = tmp / deps[j].domain;
			}
			//			System.out.println(name+ "\t"+tmpans * varprob[i][value]);
			ans = ans + tmpans * varprob[i][value];
		}
		//ans = round(ans, 4);
		//		System.out.println("P("+name+"="+value+") = " + ans);
		//		prob[value] = ans;
		return ans;
	}

	public void setValue(int value){
		for (int i=0; i<varprob.length; i++){
			for (int j=0; j<varprob.length; j++){
				if (j==value){
					varprob[i][j] = 1;
				} else {
					varprob[i][j] = 0;
				}
			}
		}
	}

	public void infer(int value){
		//we are given that the value is X
		System.out.println("Infering("+value+"): " + name);
		double Pe = computeProb(value);
		double[][] newVal = new double[deps.length][];
		for (int k=0; k<deps.length; k++){

			CopyOfNode n = deps[k];
			System.out.println("  for " + n.name);
			newVal[k] = new double[n.domain];
			for (int i=0; i<n.domain; i++){
				double Pi = n.computeProb(i);
				double Pei = getProb(value, k, i);
				double postP = (Pei*Pi) / Pe;
				newVal[k][i] = postP;
			}
		}
		for(int i=0; i<deps.length;i++){
			deps[i].updateNewStuff(newVal[i]);
		}

		setValue(value);
	}

	public void updateNewStuff(double[] newProbs){
		System.out.println("Updating: " + name);
		if (deps==null){
			System.out.print("New values: ");
			for (int i=0; i<domain; i++){
				System.out.print(newProbs[i]+" ");
				varprob[0][i] = newProbs[i];
			}
			System.out.println();
			return;
		}
		double[][] newVal = new double[deps.length][];
		for (int i=0; i<deps.length; i++){
			CopyOfNode n = deps[i];
			System.out.println("  for " + n.name);
			newVal[i] = new double[n.domain];
			for (int j=0; j<n.domain; j++){
				double postP = 0;
				for (int k=0; k<domain; k++){
					double Pe = computeProb(k);
					double Pi = n.computeProb(j);
					double newPe = newProbs[k];
					double Pei = getProb(k, i, j);
					postP = postP + Pi*((newPe*Pei)/Pe);
				}
				newVal[i][j] = postP;
				//				System.out.println("   " +i +"," +j + " - " + postP);
			}
		}
		for(int i=0; i<deps.length;i++){
			deps[i].updateNewStuff(newVal[i]);
		}
	}


	public static double round(double d, int decimalPlace){
		BigDecimal bd = new BigDecimal(Double.toString(d));
		bd = bd.setScale(decimalPlace,BigDecimal.ROUND_HALF_UP);
		return bd.doubleValue();
	}

	private double getProb(int thisVal, int other, int otherVal){
		//get P(this=thisval|other=otherval)
		//		P(x|y) = P(x^z|y)+P(x^~z|y)
		//		p(x|y^z)*p(y^z) = p(x|y^z)*p(z)*p(y)= p(x)
		//		p(x|y)*p(y) = p(x)
		//		p(x|y) = p(x|y^z)*p(z)
		double ans = 0;

		for (int i=0; i<varprob.length; i++){
			int tmp = i;
			int[] values = new int[deps.length];
			for (int j=0; j<deps.length; j++){
				values[j] = tmp % deps[j].domain; 
				tmp = tmp / deps[j].domain;
			}
			if (values[other] == otherVal){
				double mult = 1;
				for (int j=0; j<values.length; j++){
					if (j!=other){
						mult = mult*deps[j].computeProb(values[j]);
					}
				}
				ans = ans + varprob[i][thisVal]*mult;
			}
		}

		return ans;	
	}


	public double getProb(int value){
		return round(computeProb(value), 4);
	}
}
